/*
 * Javascript Agile Suite - JasFac Library
 * http://code.google.com/p/jasproject/
 *
 * Copyright (C) 2009 Luke Schafer
 * Licensed under MIT X11 license:
 * http://www.opensource.org/licenses/mit-license.php
 */

var JasFac = JasFac || {};

JasFac.Constants = {
	SINGLETON:	"singleton",
	TRANSIENT:	"transient"
};

JasFac.isExpression = function( func )
{
	if (!Jas.isFunction(func)) return false;
	var info = func.getInformation();
	if (info.name == "exp" && info.args.length == 1) return true;
	return false;
};

JasFac.Builder = (function()
{
	function _builder()
	{
		this.registrations = [];
	}

	_builder.prototype.register = function(input, initialiserType)
	{
		var activator = new JasFac.Activator(input, initialiserType);
		this.registrations[this.registrations.length] = activator;
		return activator;
	};

	_builder.prototype.registerInstance = function(input)
	{
		return this.register(input, JasFac.Activator.Constants.INSTANCE);
	};
	
	_builder.prototype.registerConfig = function(input)
	{
		return this.register(input, JasFac.Activator.Constants.CONFIG);
	};
	
	_builder.prototype.registerExpression = function(input)
	{
		return this.register(input, JasFac.Activator.Constants.EXPRESSION);
	};
	
	_builder.prototype.registerConstructor = function(input)
	{
		return this.register(input, JasFac.Activator.Constants.CONSTRUCTOR);
	};

	_builder.prototype.build = function(container, instantiateSingletons)
	{
		for (var i = 0; i < this.registrations.length; i++)
		{
			var reg = this.registrations[i];
			this.checkDependencies(reg);
			container.addActivator(this.registrations[i]);
		}

		// This will throw errors if the activations try to resolve missing components.
		if (instantiateSingletons) container.instantiateSingletons();
		//container.verifyDependencies();
	};

	_builder.prototype.checkDependencies = function(registration)
	{
		//TODO: Check any named dependencies exist
	};

	return _builder;
})();

JasFac.Activator = (function()
{
	function _activator(input, initialiserType)
	{
		this.initialiserCount = 0;

		switch(initialiserType)
		{
			case _activator.Constants.EXPRESSION:
				this.withExpression(input);
				break;

			case _activator.Constants.CONFIG:
				this.withConfig(input);
				break;

			case _activator.Constants.CONSTRUCTOR:
				this.withConstructor(input);
				break;

			case _activator.Constants.INSTANCE:
				this.withInstance(input);
				break;

			default:
				if (Jas.isFunction(input))
				{
					if (JasFac.isExpression(input))
						this.withExpression(input);
					else
						this.withConstructor(input);
				}
				else if ((input.hasOwnProperty("expression") || input.hasOwnProperty("instance") || input.hasOwnProperty("constructor")) && (input.hasOwnProperty("name") || input.hasOwnProperty("type")))
				{
					this.withConfig(input);
				}
				else
				{
					this.withInstance(input);
				}
		}
	}

	_activator.Constants = {};
	_activator.Constants.EXPRESSION = "exp";
	_activator.Constants.CONFIG = "config";
	_activator.Constants.CONSTRUCTOR = "constructor";
	_activator.Constants.INSTANCE = "instance";

	_activator.prototype.incrementInitialisers = function()
	{
		this.initialiserCount++;
		if (this.initialiserCount > 1)
			throw(Jas.Exceptions.create(
				Jas.Exceptions.Types.INVALIDARGUMENTS, 
				(this.name || "Component") + " can't be registered, too many initialisers." 
			));
		return this;
	};

	_activator.prototype.withExpression = function(expression)
	{
		this.componentExpression = expression;
		return this.incrementInitialisers();
	};

	_activator.prototype.withInstance = function(instance)
	{
		this.instance = instance;
		return this.incrementInitialisers();
	};
	
	_activator.prototype.withConstructor = function(constructor, args)
	{
		this.componentConstructor = constructor;
		this.as(constructor);
		this.injectArguments(args);
		return this.incrementInitialisers();
	};

	_activator.prototype.withConfig = function(input)
	{
		if (input.hasOwnProperty("expression"))
		{
			this.componentExpression = input.expression;
			this.incrementInitialisers();
		}
		if (input.hasOwnProperty("instance"))
		{
			this.instance = input.instance;
			this.incrementInitialisers();
		}
		if (input.hasOwnProperty("constructor"))
		{
			this.componentConstructor = input.constructor;
			this.incrementInitialisers();			
		}
		if (input.hasOwnProperty("name")) this.named(input.name);
		if (input.hasOwnProperty("type")) this.as(input.type);
		if (input.hasOwnProperty("properties")) this.injectProperties(input.properties);
		if (input.hasOwnProperty("arguments")) this.injectProperties(input.arguments);
		if (input.hasOwnProperty("scope")) this.scoped(input.scope);

		return this;
	};

	_activator.prototype.named = function(name)
	{
		this.name = name;
		return this;
	};
	
	_activator.prototype.as = function(type)
	{
		this.type = type;
		return this;
	};
	
	_activator.prototype.scoped = function(scope)
	{
		this.scope = scope;
		return this;
	};
	
	_activator.prototype.injectProperties = function(props)
	{
		this.injectedProperties = props;
		return this;
	};

	_activator.prototype.injectArguments = function(args)
	{
		this.injectedArguments = args;
		return this;
	};

	_activator.prototype.verifyDependencies = function(container)
	{
		if (this.componentConstructor)
		{
			var toCheck = this.componentConstructor.getInformation().args;
			for (var i = 0; i < toCheck.length; i++)
			{
				var name = toCheck[i];
				if (!this.injectedArguments[name] && !container.contains(name))
					throw(Jas.Exceptions.create(
						Jas.Exceptions.Types.NOTREGISTERED, 
						((typeof name == 'string') ? "'" + name + "'": "Type") + " could not be found in the container."
					));
			}
		}
	};

	_activator.prototype.autowireConstructor = function(container, extraArgs)
	{
		this.injectedArguments = this.injectedArguments || {};
		extraArgs = extraArgs || {};

		var args = this.componentConstructor.getInformation().args;
		var argsToUse = [];
		var commands = [];

		for (var i = 0; i < args.length; i++)
		{
			if (extraArgs[args[i]] != undefined)
			{
				argsToUse[i] = JasFac.isExpression(extraArgs[args[i]]) 
					? extraArgs[args[i]](container) 
					: extraArgs[args[i]];
			} 
			else if (this.injectedArguments[args[i]] != undefined)
			{
				argsToUse[i] = JasFac.isExpression(this.injectedArguments[args[i]]) 
					? this.injectedArguments[args[i]](container) 
					: this.injectedArguments[args[i]];
			}
			else
			{
				argsToUse[i] = container.resolve(args[i]);
			}
			commands[commands.length] = "argsToUse[" + i + "]";
		}

		var ret;
		var construct = this.componentConstructor;
		eval(Jas.stringFormat("ret = new construct({0});", commands.join(",")));
		return ret;
	};

	_activator.prototype.activate = function(container, extraArgs)
	{
		try
		{
			var instance = null;

			if (this.instance)
			{
				if (this.scope == JasFac.Constants.SINGLETON) return this.instance;
				instance = this.instance;
			}
			else if (this.componentExpression)
			{
				instance = this.componentExpression(container);
			}
			else if (this.componentConstructor)
			{
				instance = this.autowireConstructor(container, extraArgs);
			}

			for (var i in this.injectedProperties)
			{
				if (this.injectedProperties.hasOwnProperty(i))
				{
					instance[i] = JasFac.isExpression(this.injectedProperties[i]) 
						? this.injectedProperties[i](container) 
						: container.resolve(this.injectedProperties[i]);
				}
			}

			if (this.scope == JasFac.Constants.SINGLETON)
			{
				this.instance = instance;
			}

			return instance;
		}
		catch (ex)
		{
			if (ex.type == Jas.Exceptions.Types.NOTREGISTERED)
			{
				throw(Jas.Exceptions.create(
					Jas.Exceptions.Types.CANTRESOLVE, 
					(this.name || "Type") + " can't be resolved, a dependency was missing: " + ex.message,
					ex
				));
			}
			throw(ex);
		}
	};

	return _activator;
})();

JasFac.Container = (function()
{
	function _container()
	{
		this.names = {};
		this.types = [];
	}

	_container.prototype.resolve = function(name, injectArgs)
	{
		var activator = (typeof name == 'string') ? this.names[name] : this.getType(name);

		if (activator === null || typeof activator == 'undefined')
		{
			throw(Jas.Exceptions.create(
				Jas.Exceptions.Types.NOTREGISTERED, 
				((typeof name == 'string') ? "'" + name + "'" : "Type") + " could not be found in the container."
			));
		}

		return activator.activate(this, injectArgs);
	};

	_container.prototype.resolveOptional = function(name, injectArgs)
	{
		var activator = (typeof name == 'string') ? this.names[name] : this.getType(name);

		if (activator === null || typeof activator == 'undefined')
			return null;

		return activator.activate(this, injectArgs);
	};

	_container.prototype.contains = function(name)
	{
		var activator = (typeof name == 'string') ? this.names[name] : this.getType(name);
		if (activator === null || typeof activator == 'undefined') return false;
		return true;
	};

	_container.prototype.verifyDependencies = function()
	{
		var i;
		for (i in this.names)
		{
			if (this.names.hasOwnProperty(i))
			{
				this.names[i].verifyDependencies(this);
			}
		}
		for (i = 0; i < this.types.length; i++)
		{
			this.types[i].verifyDependencies(this);
		}
	};

	_container.prototype.addActivator = function(activator)
	{
		if (activator.name) this.names[activator.name] = activator;
		if (activator.type) this.types[this.types.length] = activator;

	};

	_container.prototype.instantiateSingletons = function()
	{
		function createInstance(item)
		{
			if (item.scope == JasFac.Constants.SINGLETON && !item.instance)
				item.activate(this);
		}

		var i;
		for (i in this.names)
		{
			if (this.names.hasOwnProperty(i))
			{
				ceateInstance(this.names[i]);
			}
		}

		for (i = 0; i < this.types.length; i++)
		{
			createInstance(this.types[i]);
		}
	};

	_container.prototype.getType = function(type)
	{
		for (var i = 0; i < this.types.length; i++)
		{
			if (this.types[i].type == type) return this.types[i];
		}
	};

	return _container;
})();